#include "reqrep.h"

#include <QMetaObject>
#include <QDebug>
#include <QThread>

ReqRep::ReqRep(QObject *parent)
    : QObject{parent}
{
    addResponder(Subject_TestMethod, this, REQREP_METHOD(onTestRep));

    testReply = addResponder(Subject_TestFunction, REQREP_REPLY {
        ok = true;
        return QString("TestFunction:%1").arg(subject);
    });
}

ReqRep::~ReqRep()
{
    removeResponder(this);
    removeResponder(Subject_TestFunction, testReply);
}

ReqRep &ReqRep::getInstance()
{
    static ReqRep reqrep;
    return reqrep;
}

void ReqRep::addResponder(int subject, QObject *responder, const char *replyMethod)
{
    RepInfo info(responder, replyMethod);

    mutex.lock();
    responders.insert(subject, info);
    mutex.unlock();
}

ReqRep::Reply ReqRep::addResponder(int subject, Reply reply)
{
    RepInfo info(reply);

    mutex.lock();
    responders.insert(subject, info);
    mutex.unlock();

    return reply;
}

void ReqRep::removeResponder(int subject, const QObject *responder)
{
    mutex.lock();
    QList<RepInfo> infos = responders.values(subject);

    for(auto &info: infos) {
        if(info.responder == responder) {
            responders.remove(subject, info);
        }
    }

    mutex.unlock();
}

void ReqRep::removeResponder(const QObject *responder)
{
    mutex.lock();
    auto subjects = responders.uniqueKeys();
    mutex.unlock();

    for(auto &subject: subjects) {
        removeResponder(subject, responder);
    }
}

void ReqRep::removeResponder(int subject, const Reply reply)
{
    RepInfo info(reply);

    mutex.lock();
    responders.remove(subject, info);
    mutex.unlock();
}

void ReqRep::removeResponder(const Reply reply)
{
    RepInfo info(reply);
    auto subjects = responders.uniqueKeys();

    for(auto &subject: subjects) {
        removeResponder(subject, reply);
    }
}

static inline void safelySet(bool * const ptr, const bool value) {
    if(ptr) {
        *ptr = value;
    }
}

QVariant ReqRep::request(int subject, bool *ok)
{
    Subject s = static_cast<ReqRep::Subject>(subject);
    bool retOk = true;
    QVariant ret;

    safelySet(ok, false);
    if(mutex.tryLock() == false) {
        return ret;
    }

    do {
        auto infos = responders.values(s);
        if(infos.count() == 0) {
            retOk = false;
            break;
        }

        auto info = infos.at(0);
        if(info.isMethod) {
            Q_ASSERT(info.responder != nullptr && info.method != nullptr);

            Qt::ConnectionType connectionType = Qt::DirectConnection;
            if(QThread::currentThread() != info.responder->thread()) {
                connectionType = Qt::BlockingQueuedConnection;
            }

            bool invoded = QMetaObject::invokeMethod(info.responder, info.method, connectionType,
                                                     Q_RETURN_ARG(QVariant, ret),
                                                     Q_ARG(int, subject),
                                                     Q_ARG(bool&, retOk));
            if(!invoded) {
                retOk = false;
            }
        } else {
            ret = info.reply(subject, retOk);
        }
    }while(0);
    mutex.unlock();

    safelySet(ok, retOk);
    return ret;
}

QVariant ReqRep::onTestRep(int subject, bool &ok)
{
    ok = true;
    return QString("TestMethod:%1").arg(subject);
}
